﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using Cubicle.Core.Config;
using Cubicle.Core.DataContracts;
using Cubicle.Core.Hosting;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Shared;

namespace Cubicle.Core.Repository
{
    /// <summary>
    /// When the 'InstanceContextMode' attribute is set to '.Single'
    /// the lifetime of the Service will automatically be managed by IIS
    /// and not forwarded to the appropriate ServiceFactory (in this case,
    /// Autofac). 
    /// 
    /// WCF will complain that there is no parameter-less constructor.
    /// 
    /// By setting the InstanceContextMode to .PerSession, the actual
    /// component lifetime is managed by the IoC container, where it is 
    /// set to SingleInstance, which effectively means lifetime management
    /// is handled by the IoC instead of WCF.
    /// </summary>
    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.Single, 
        Namespace = "http://www.cubicle.co.uk/",
        Name = "ReflectionEngine")]
    //[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
    //[ReflectKnownType]
    [UseNetDataContractSerializer]
    [Export(typeof(IReflectionEngine))]
    public class ReflectionEngine : MarshalByRefObject, IReflectionEngine
    {

        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        [Import] private IRepository _repository;

        [ImportingConstructor]
        public ReflectionEngine(EngineConfig config)
        {
            SetLoadRunnerState(true);

            // Initialise event hooks
            AppDomain.CurrentDomain.AssemblyLoad += RunOnLoad;
        }

        #region Overrides

        public override object InitializeLifetimeService()
        {
            // Give this object an infinite lifetime
            return null;
        }

        #endregion

        #region Implementation of IEngine:Reflection

        public Dictionary<string, string> GetHandleTypes()
        {
            return _repository
                .List()
                .ToDictionary(h => h, h => _repository.Get(h).GetType().Name);
        }

        public Dictionary<string, string> GetMetaDataTypes(string handle)
        {
            var robj = _repository.GetRObject(handle);
            if (robj == null || robj.Metadata.Count == 0) return null;
            return robj.Metadata.ToDictionary(o => o.Key, o => o.Value.ToString());
        }

        public List<MemberData> GetMembers(string handle)
        {
            var obj = _repository.Get(handle);
            return obj == null
                       ? null
                       : obj.GetType().GetMembers().Select((m, i) => new MemberData(i, m)).ToList();
        }

        public IEnumerable<AssemblyData> GetAssemblyDetails()
        {
            return Helpers.GetAssemblyDetails().ToList();
        }

        public TypeData GetTypeData(string target)
        {
            var t = GetTargetType(target);
            if (t == null) return null;
            return new TypeData(t);
        }

        private Type GetTargetType(string target)
        {
            var obj = _repository.Get(target);
            return obj == null 
                ? Type.GetType(target) 
                : obj.GetType();
        }

        public List<TypeData> GetTypes(string fullname)
        {
            var asm = AppDomain.CurrentDomain
                .GetAssemblies()
                .Where(a => a.FullName == fullname).FirstOrDefault();
            if (asm == null) return null;

            try
            {
                var types = asm
                    .GetTypes()
                    .Select(t => new TypeData(t))
                    .ToList();

                return types;
            }
            catch (Exception e)
            {
                _logger.ErrorException(e, "Unable to reflect types in assembly. Check if all dependencies are loaded. FullName = {0}", asm.FullName);
                return null;
            }
        }

        public List<MethodData> GetMethods(string target)
        {
            var t = GetTargetType(target);
            if (t == null) return null;
            var methods = t.GetMethods().Select(m => new MethodData(m)).ToList();

            // Duplicate the list
            var source = new List<MethodData>();
            source.AddRange(methods);

            // Update the offset
            while (source.Count > 0)
            {
                var offset = 0;
                var subset = source.Where(m => m.Name == source[0].Name).ToList();
                foreach (var c in subset)
                {
                    c.Offset = offset;
                    offset++;
                    source.Remove(c);
                }
            }

            // Return the methods
            return methods;
        }

        public List<FieldData> GetFields(string target)
        {
            var t = GetTargetType(target);
            if (t == null) return null;
            return t
                .GetFields()
                .Select(x => new FieldData(x))
                .ToList();
        }

        public List<PropertyData> GetProperties(string target)
        {
            var t = GetTargetType(target);
            if (t == null) return null;
            return t
                .GetProperties()
                .Select(x => new PropertyData(x))
                .ToList();
        }

        public List<ConstructorData> GetConstructors(string target)
        {
            var t = GetTargetType(target);
            if (t == null) return null;
            return t
                .GetConstructors()
                .Select(ConstructorData.Create)
                .ToList();
        }

        public List<ParameterData> GetParameterData(string target, string methodName, int offset)
        {
            var t = GetTargetType(target);
            if (t == null) return null;
            var methods = t.GetMethods()
                .Where(m => m.Name == methodName)
                .ToArray();
            if (methods.Count() <= offset) return null;
            return methods.Where((m, i) => i == offset)
                .FirstOrDefault()
                .GetParameters()
                .Select((p, i) => new ParameterData(i, p))
                .ToList();
        }

        public void RemoveHandle(string handle)
        {
            _repository.Remove(handle);
        }

        public string LoadAssembly(byte[] assembly, byte[] symbols)
        {
            // Now load more assemblies
            return TypeCache.LoadAssembly(assembly, symbols);
        }

        #endregion

        #region Implementation of IEngine:Misc

        public bool LoadRunnerState { get; set; }

        public bool GetLoadRunnerState() { return LoadRunnerState; }
        
        public string GetVersion()
        {
            return Assembly.GetExecutingAssembly().GetName().Version.ToString();
        }

        public void SetLoadRunnerState(bool state) { LoadRunnerState = state; }

        public IEnumerable<string> GetLog(int i)
        {
            return Helpers.GetMemoryLog(i);
        }

        protected void RunOnLoad(object sender, AssemblyLoadEventArgs args)
        {
            if (!LoadRunnerState) return;

            List<string> missing;
            var safeTypes = args.LoadedAssembly.SafeGetTypes(out missing, false);
            if (safeTypes == null || safeTypes.Count() == 0) return;

            var loadRunners = safeTypes
                    .Where(type => type.GetInterfaces().Contains(typeof(ILoadRunner)));

            foreach (var loadRunner in loadRunners)
            {
                try
                {
                    var runner = (ILoadRunner)Activator.CreateInstance(loadRunner);
                    runner.RunOnLoad(this);
                }
                catch (Exception e)
                {
                    _logger.ErrorException(e, "Error executing 'ILoadRunner.RunOnLoad'. Assembly = {0}, Type = {1}.",
                                  args.LoadedAssembly.FullName, loadRunner.FullName);
                }
            }
        }

        public string Id { get; set; }

        public string GetId() { return Id; }

        #endregion

    }
}